#ifndef     __SYNC_H
#define     __SYNC_H

#include <chrono>
#include <mutex>
#include <condition_variable>

extern "C" {
#include <libavutil/rational.h>
}

class Semaphore {

public:
    void acquire(int value) {
        std::unique_lock<std::mutex> lock(m_mutex);
        m_cond.wait(lock, [this, value](){
            return m_count >= value;
        });
        m_count -= value;
    }

    void signal() {
        std::unique_lock<std::mutex> lock(m_mutex);
        m_count += 1;
        m_cond.notify_one();
    }

private:
    int m_count = 0;
    std::mutex m_mutex;
    std::condition_variable m_cond;
};

class UiSync {
public:
    void reset() {
        m_flag = false;
    }

    void await() {
        std::unique_lock<std::mutex> lock(m_mutex);
        m_cond.wait(lock, [this]() {
            return m_flag;
        });
    }

    void signal() {
        std::unique_lock<std::mutex> lock(m_mutex);
        m_flag = true;
        m_cond.notify_one();
    }

private:
    std::mutex m_mutex;
    std::condition_variable m_cond;
    bool m_flag = false;
};

class AVSync {

public:
    using clock = std::chrono::high_resolution_clock;
    static constexpr int64_t NAN_SEC = 1000 * 1000 * 1000;

    AVSync(AVRational video, AVRational audio) : 
        m_video_time_base(video),
        m_audio_time_base(audio),
        m_base( clock::now() )
    {

    }

    void update() {
        m_base = clock::now();
    }

    void exit() {
        std::unique_lock<std::mutex> lock(m_mutex);
        m_exit_flag = true;
        m_video_cond.notify_one();
        m_audio_cond.notify_one();
    }

    bool wait_audio(int64_t pts) {
        if (m_exit_flag) {
            return false;
        }
        int64_t nan_sec = pts * av_q2d(m_audio_time_base) * NAN_SEC;
        clock::time_point tp = m_base + std::chrono::nanoseconds(nan_sec);
        clock::time_point now = clock::now();
        if (tp <= now) {
            return true;
        }
        std::unique_lock<std::mutex> lock(m_mutex);
        m_audio_cond.wait_for(lock, tp - now, [this]() {
            return m_exit_flag;
        });
        return !m_exit_flag;
    }

    bool wait_video(int64_t pts) {
        if (m_exit_flag) {
            return false;
        }
        int64_t nan_sec = pts * av_q2d(m_video_time_base) * NAN_SEC;
        clock::time_point tp = m_base + std::chrono::nanoseconds(nan_sec);
        clock::time_point now = clock::now();
        if (tp <= now) {
            return true;
        }
        std::unique_lock<std::mutex> lock(m_mutex);
        m_video_cond.wait_for(lock, tp - now, [this]() {
            return m_exit_flag;
        });
        return !m_exit_flag;
    }

private:
    AVRational m_video_time_base;
    AVRational m_audio_time_base;
    clock::time_point m_base;
    std::mutex m_mutex;
    std::condition_variable m_video_cond;
    std::condition_variable m_audio_cond;
    bool m_exit_flag = false;
};

class MuteSync {

public:
    using clock = std::chrono::high_resolution_clock;
    static constexpr int64_t NAN_SEC = 1000 * 1000 * 1000;

    MuteSync(AVRational video) : 
        m_video_time_base(video),
        m_base( clock::now() )
    {

    }

    void update() {
        m_base = clock::now();
    }

    void exit() {
        std::unique_lock<std::mutex> lock(m_mutex);
        m_exit_flag = true;
        m_video_cond.notify_one();
    }

    bool wait_video(int64_t pts) {
        if (m_exit_flag) {
            return false;
        }
        int64_t nan_sec = pts * av_q2d(m_video_time_base) * NAN_SEC;
        clock::time_point tp = m_base + std::chrono::nanoseconds(nan_sec);
        clock::time_point now = clock::now();
        if (tp <= now) {
            return true;
        }
        std::unique_lock<std::mutex> lock(m_mutex);
        m_video_cond.wait_for(lock, tp - now, [this]() {
            return m_exit_flag;
        });
        return !m_exit_flag;
    }

private:
    AVRational m_video_time_base;
    clock::time_point m_base;
    std::mutex m_mutex;
    std::condition_variable m_video_cond;
    bool m_exit_flag = false;
};



#endif
